package cn.seaboot.admin.mvc.async;

import cn.seaboot.commons.exception.BizException;
import cn.seaboot.commons.thread.CustomThreadFactory;

import java.util.concurrent.*;

/**
 * 功能与 ScheduledThreadPoolExecutor 相同，队列总长度做了限制，用于制作短期的定时任务;
 * maxDelaySecond参数为最大延迟时间，舍弃全部大于30分钟的任务;
 * 系统重启后，丢失全部定时任务;
 *
 * @author Mr.css
 * @version 2019/12/6 15:25
 */
public class ScheduledTask {
    private final ScheduledThreadPoolExecutor scheduledExecutorService;
    private int maxQueueSize = 300;
    private int corePoolSize = 10;
    private int maxDelaySecond = 30;

    /**
     * 使用默认的配置创建计划任务
     *
     * @param maxQueueSize   未执行的任务会长期占用内存，一次最多存储任务数
     * @param corePoolSize   并发线程数
     * @param maxDelaySecond 最大等待
     */
    ScheduledTask(int maxQueueSize, int corePoolSize, int maxDelaySecond) {
        this.maxQueueSize = maxQueueSize;
        this.corePoolSize = corePoolSize;
        this.maxDelaySecond = maxDelaySecond;
        scheduledExecutorService = new ScheduledThreadPoolExecutor(
                corePoolSize, new CustomThreadFactory("scheduled-task"));
    }

    ScheduledTask() {
        scheduledExecutorService = new ScheduledThreadPoolExecutor(
                corePoolSize, new CustomThreadFactory("scheduled-task"));
    }

    public int getMaxQueueSize() {
        return maxQueueSize;
    }

    public void setMaxQueueSize(int maxQueueSize) {
        this.maxQueueSize = maxQueueSize;
    }

    public int getCorePoolSize() {
        return corePoolSize;
    }

    public void setCorePoolSize(int corePoolSize) {
        this.corePoolSize = corePoolSize;
    }

    private void validCheck(long delay, TimeUnit unit) {
        if (scheduledExecutorService.getQueue().size() > maxQueueSize) {
            throw new BizException("已超出定时任务最大容量，无法继续添加定时任务!");
        }
        double second = TimeUnit.SECONDS.convert(delay, unit);
        if (second > maxDelaySecond) {
            throw new BizException("定时任务大于：" + maxDelaySecond + "分钟");
        }
    }

    /**
     * 可以查看内部全部计划任务，尽可能使用Task创建计划任务，使用Task可以创建带有Name字段的Runnable对象;
     *
     * @return BlockingQueue<Runnable>
     */
    public BlockingQueue<Runnable> getTasks() {
        return scheduledExecutorService.getQueue();
    }

    /**
     * 执行计划任务
     *
     * @param command runnable
     * @param delay   延迟时间
     * @param unit    时间单位
     * @return 计划任务
     */
    public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
        validCheck(delay, unit);
        return scheduledExecutorService.schedule(command, delay, unit);
    }

    /**
     * 执行计划任务
     *
     * @param callable 回调
     * @param delay    延迟时间
     * @param unit     时间单位
     * @return 计划任务
     */
    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
        validCheck(delay, unit);
        return scheduledExecutorService.schedule(callable, delay, unit);
    }

    /**
     * 与scheduleWithFixedDelay不同，会等任务全部执行完，等待5秒，然后到下一次循环；
     *
     * @param command      Runnable
     * @param initialDelay 最开始等待的时间
     * @param period       每次循环的时间
     * @param unit         时间单位
     * @return 计划任务
     */
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
        validCheck(period + initialDelay, unit);
        return scheduledExecutorService.scheduleAtFixedRate(command, initialDelay, period, unit);
    }

    /**
     * 循环执行计划任务，如果并发线程已满，第二次循环，会从先执行完的任务开始，等待delay的时间，开始执行任务；
     * 线程池容量为3，需要执行5个任务，其中3个先执行，之后2个执行；
     * 前面3个任务执行超时，等待3个任务全部完成，之后立即执行后面2个任务；
     * 等后面2个任务全部执行结束，再开启新的循环。
     *
     * @param command      Runnable
     * @param initialDelay 最开始等待的时间
     * @param delay        每次循环的时间
     * @param unit         时间单位
     * @return 计划任务
     */
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
        validCheck(delay + initialDelay, unit);
        return scheduledExecutorService.scheduleWithFixedDelay(command, initialDelay, delay, unit);
    }
}
